/* LoadUpdateThread.java

{{IS_NOTE
	Purpose:

	Description:

	History:
		Oct 8, 2007 9:58:59 AM     2007, Created by Dennis.Chen
}}IS_NOTE

Copyright (C) 2007 Potix Corporation. All Rights Reserved.

{{IS_RIGHT
	This program is distributed under GPL Version 2.0 in the hope that
	it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.imdm.portal.loadchecker.ui;


import java.util.Enumeration;
import java.util.Random;
import java.io.IOException;
import java.lang.Thread;
import java.util.Hashtable;
import java.util.concurrent.Semaphore;
import java.util.TimerTask;
import java.util.Timer;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zul.ListModel;
import org.zkoss.zul.ListModelList;
import org.zkoss.zkex.zul.SimpleListModelSharer;
//import org.zkoss.zulex.SimpleListModelSharer;

import org.imdm.portal.loadchecker.telnet.Command;
import org.imdm.portal.loadchecker.telnet.ReadVersionCommand;
import org.imdm.portal.loadchecker.telnet.WhoCommand;
import org.imdm.portal.loadchecker.telnet.ReadUserCommand;
import org.imdm.portal.loadchecker.telnet.ReadLinkTimeCommand;
import org.imdm.portal.loadchecker.telnet.UnameCommand;
import org.imdm.portal.loadchecker.telnet.TelnetClientAdvance;
import org.imdm.portal.loadchecker.telnet.TelnetException;

/**
 * @author James.Sun
 *
 */
public class LoadUpdateService {
	final static private String _LOGIN = "root";
	final static private String _SERVERS_XML_FILE = "WEB-INF/mdmservers.xml";
	final static private String _SERVER_TAG = "SERVER";
	final static private String _SERVER_NAME_ATTR = "name";

	ListModelList loadModel = null;
	SimpleListModelSharer modelSharer = null;
	Random random = new Random(System.currentTimeMillis());
	static LoadUpdateService instance = null;
	int minutes_since_refresh = 0;
	Timer  refreshTimer = null;

	private Hashtable<String, UpdateThread> threadTable = new Hashtable<String, UpdateThread>();

	synchronized public static LoadUpdateService lookup(Desktop d){
		if(instance==null){
			instance = new LoadUpdateService(d);
		}
		return instance;
	}

	private LoadUpdateService(Desktop d){
		loadModel = new ListModelList();
		modelSharer = new SimpleListModelSharer(loadModel);

		if ( !addServersFromXml(d) ) {
			//this.addServer("zcyds4d0");
			this.addServer("zcyds4d1");
			this.addServer("zcyds4d2");
			this.addServer("zcyds4d3");
			this.addServer("zcyds4d4");
			this.addServer("zcyds4d5");
			this.addServer("zcyds4d6");
			this.addServer("zcyds4d7");
			this.addServer("zcyds4d9");
		}

		//create the timer, repeat every 1 minute.
		refreshTimer = new Timer(true);
		refreshTimer.scheduleAtFixedRate(new MinuteSinceRefreshTimerTask(), 60000, 60000);
	}

	private boolean addServersFromXml(Desktop d) {
		if ( d == null )
			return false;
		
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder;
		try {
			builder = factory.newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
			return false;
		}

		Document doc;
		try {
			String path = d.getWebApp().getRealPath(d.getCurrentDirectory());
			doc = builder.parse(path + _SERVERS_XML_FILE);
		} catch (SAXException e) {
			e.printStackTrace();
			return false;
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}

		doc.normalize();
		NodeList serverList = doc.getElementsByTagName(_SERVER_TAG);
		for (int idx = 0; idx < serverList.getLength(); idx++) {
			try {
				Element elm = (Element) serverList.item(idx);
				String servername = elm.getAttribute(_SERVER_NAME_ATTR);
				this.addServer(servername);
			} catch (java.lang.NullPointerException e) {
				e.printStackTrace();
				return false;
			}
		}
		return true;
	}

	public ListModel getProxy(Desktop desktop){
		return modelSharer.getProxy(desktop);
	}

	public void addServer(String server){
		synchronized(loadModel){
			loadModel.add(
				new LoadInfo(server, LoadInfo._UNKNOWN, LoadInfo._UNKNOWN, LoadInfo._UNKNOWN, 0)
			);
		}
		UpdateThread updateThread = new UpdateThread(server, _LOGIN, getPassword(server));
		updateThread.start();
		threadTable.put(server, updateThread);
	}
	
	private String getPassword(final String server){
		StringBuffer _buf = new StringBuffer("Mdim");
		try {
			_buf.append(server.substring(server.length()-3));
		}catch(IndexOutOfBoundsException e) {
	        System.err.println("Invalid server name: " + server);
			e.printStackTrace();
		}
		return _buf.toString();
	}

	public void refresh() {
		UpdateThread thd1;
		for (Enumeration<UpdateThread> e = threadTable.elements(); e.hasMoreElements() ;){
			thd1 = e.nextElement();
System.err.println(thd1.getState() + " " + thd1.getServerName());
		    thd1.refreshMe();
		}
		minutes_since_refresh = 0;
	}

	public void clearAll(){
		synchronized(loadModel){
			loadModel.clear();
		}
	}

	public void clearRandom(){
		synchronized(loadModel){
			int index = random.nextInt(loadModel.size());
			loadModel.remove(index);
		}
	}

	protected int getServerIndex(String sn) {
		for(int idx=0; idx<loadModel.size(); idx++) {
			LoadInfo li = (LoadInfo)loadModel.get(idx);
			if (sn == li.getServer() )
				return idx;
		}
		return -1;
	}

	class MinuteSinceRefreshTimerTask extends TimerTask {
		public void run() {
			minutes_since_refresh++;
		}
	}
	
	public int getMinutesSinceRefresh() {
		return minutes_since_refresh;
	}

	class UpdateThread extends Thread {

		private String serverName;
		private String user;
		private String password;
		private Semaphore sem;

		//private int maxLogins = 10;

		private Command readVersionCommand;
		private WhoCommand userNumberCommand;
		private Command readLinktimeCommand;
		private Command readUserCommand;
		private Command unameCommand;
		private TelnetClientAdvance telnetClient;

		boolean running = false;
        UpdateThread(String serverName, String l, String p) {
        	this.serverName = serverName;
        	this.user = l;
        	this.password = p;

        	telnetClient = new TelnetClientAdvance();
        	telnetClient.setServer(this.serverName);
        	telnetClient.setUser(this.user);
        	telnetClient.setPassword(this.password);

        	readVersionCommand = new ReadVersionCommand(telnetClient);
        	userNumberCommand = new WhoCommand(telnetClient);
        	readUserCommand = new ReadUserCommand(telnetClient);
        	readLinktimeCommand = new ReadLinkTimeCommand(telnetClient);
        	unameCommand = new UnameCommand(telnetClient);
        }

		public String getServerName() {
			return serverName;
		}

		public void run() {
			int idx = getServerIndex(this.serverName);
		    if (idx < 0) {
		        System.err.println("Could not found " + this.serverName);
		    	return;
		    }
		    sem = new Semaphore(1);
		    while (true) {
		    	try {
					sem.acquire();
				} catch (InterruptedException e1) {
			        System.err.println("Could not sem.acquire() " + this.serverName);
					e1.printStackTrace();
				}
				LoadInfo li = (LoadInfo)loadModel.get(idx);
                li.setLinker(LoadInfo._UPDATING);
                li.setLoad(LoadInfo._UPDATING);
                li.setLinktime(LoadInfo._UPDATING);
                li.setLogins(LoadInfo._INVALID_LOGIN);
                li.setServerDetails(LoadInfo._UPDATING);
			    synchronized(loadModel){
			    	loadModel.set(idx, li);
			    }
			    try {
			    	li = getCurrentLoadInfo(this.serverName);
				    synchronized(loadModel){
				    	loadModel.set(idx, li);//and update it
		            }
				} catch(TelnetException e) {
			        System.err.println("Can not get load info from " + this.serverName);
	    		    System.err.println(e.getMessage());
                    li.setLinker(LoadInfo._UNKNOWN);
	                li.setLoad(LoadInfo._UNKNOWN);
	                li.setLinktime(LoadInfo._UNKNOWN);
	                li.setLogins(LoadInfo._INVALID_LOGIN);
	    		    li.setServerDetails(LoadInfo._UNKNOWN);
				    synchronized(loadModel){
		                loadModel.set(idx, li);
				    }
				} finally {
					telnetClient.disconnect();
				}
		    }
		}

		public void refreshMe() {
			sem.release();
		}
		/**
		 * @param si
		 * @return
		 */
		private LoadInfo getCurrentLoadInfo(String sn) throws TelnetException  {
			String version = readVersionCommand.execute();
			String linktime = readLinktimeCommand.execute();
			String linker = readUserCommand.execute();
			String userNum = userNumberCommand.execute();
			String serverinfo = unameCommand.execute();

		    int logins = (new Integer(userNum)).intValue();
		    LoadInfo li = new LoadInfo(this.serverName, version, linker, linktime, logins);
		    li.setServerDetails(serverinfo);
		    li.setLogin_names(userNumberCommand.getUserNames());
		    li.setLogin_entities(userNumberCommand.countSumEntities());
		    return li;
		}
	}
}

